package com.xcity.tools.proto;

import java.beans.PropertyDescriptor;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;

public class ProtoEntry {

	public final File protoFile;
	public final String nativeClass; // the native request/response/push class
	public final String protoClass; // the proto class
	// factory.proto.builder
	public final List<Property> fields;
	
	public static class Property {
		public String name;
		public PropertyInvoke invoke;
		public Property(PropertyDescriptor propertyDes) {
			this.name = propertyDes.getName();
			this.invoke = new PropertyInvoke();
			Method getter = propertyDes.getReadMethod();
			if (getter != null) {
				this.invoke.getter = getter.getName();
			}
			Method setter = propertyDes.getWriteMethod();
			if (setter != null) {
				this.invoke.setter = setter.getName();
			}
		}
		public Property(Field field) {
			if (!Modifier.isPublic(field.getModifiers())) {
				throw new RuntimeException("field not public: " + field.getDeclaringClass() + "->" + field.getName());
			}
			this.name = field.getName();
			this.invoke = new PropertyInvoke();
		}
	}
	
	public static class PropertyInvoke {
		public String setter, getter;
	}
	
	public ProtoEntry(File protoFile, Class<?> nativeClass) {
		this.protoFile = protoFile;
		this.nativeClass = nativeClass.getName().replace('$', '.');
		String s = ProtobufUtils.getProtoClassName(nativeClass);
		this.protoClass = ProtobufUtils.protoClassPackage + '.' + s + "Factory." + s;
		this.fields = new ArrayList<Property>();
	}
	
	public void createInterpreterClass() {
		String className = this.protoClass + "Interpreter";
		int n = protoClass.lastIndexOf('.');
		if (n != -1) {
			className = className.substring(n + 1);
		}
		String classFullName = ProtobufUtils.protoInterpreterPackage + '.' + className;
		File file = new File(ProtobufUtils.protoJavaCodeDir, classFullName.replace('.', '/') + ".java");
		FileWriter fw = new FileWriter(file);
		// package
		fw.newLine();
		fw.writeLine("// Generated by the com.xcity.tools.proto.ProtobufUtils.  DO NOT EDIT!");
		fw.write("// ").writeLine(protoFile.getName());
		fw.write("package ").write(ProtobufUtils.protoInterpreterPackage).writeLine(';').newLine();
		// class begin
		fw.write("public class ").writeLine(className)
			.writeLine("implements").writeLine("com.xcity.proto.ProtoInterpreter").writeLine('<')
			.write(protoClass).writeLine(", ").writeLine(nativeClass).writeLine("> {").newLine();
		// implement method toProto
		fw.writeLine("\t@Override");
		fw.write("\tpublic ").write(protoClass).write(" toProto(").write(nativeClass).writeLine(" nativeObj) {").newLine();
		createToProto(fw);
		fw.newLine();
		fw.write("\t}").newLine();
		// implement method toNative
		fw.newLine();
		fw.writeLine("\t@Override");
		fw.write("\tpublic ").write(nativeClass).write(" toNative(").write(protoClass).writeLine(" protoObj) {").newLine();
		createToNative(fw);
		fw.newLine();
		fw.write("\t}").newLine();
		// class end
		fw.writeLine('}');
		fw.flush();
		System.out.println("interpreter: " + file.getAbsolutePath());
	}
	
	private void createToProto(FileWriter fw) {
		// native obj->proto obj(factory.obj.newBuilder().setXX().build())
		fw.write("\t\t").write(protoClass).writeLine(" protoObj =");
		fw.write("\t\t\t").write(protoClass).writeLine(".newBuilder()");
		for (Property prpt : fields) {
			String setter = "set" + prpt.name.substring(0, 1).toUpperCase() + prpt.name.substring(1);
			String getter = "";
			if (prpt.invoke.getter != null) {
				getter = prpt.invoke.getter + "()";
			} else {
				getter = prpt.name; // must be public field
			}
			fw.write("\t\t\t.").write(setter).write("(nativeObj.").write(getter).writeLine(")");
		}
		fw.writeLine("\t\t\t.build();");
		fw.writeLine("\t\treturn protoObj;");
	}
	
	private void createToNative(FileWriter fw) {
		// proto obj->native obj(setXX or field=xx)
		fw.write("\t\t").write(nativeClass).writeLine(" nativeObj =");
		fw.write("\t\t\t").write("new ").write(nativeClass).writeLine("();");
		for (Property prpt : fields) {
			String getter = prpt.invoke.getter;
			if (getter == null) {
				getter = "get" + prpt.name.substring(0, 1).toUpperCase() + prpt.name.substring(1);
			}
			getter += "()";
			if (prpt.invoke.setter != null) {
				fw.write("\t\tnativeObj.").write(prpt.invoke.setter).write("(protoObj.").write(getter).writeLine(");");
			} else {
				// public field
				fw.write("\t\tnativeObj.").write(prpt.name).write(" = protoObj.").write(getter).writeLine(";");
			}
		}
		fw.writeLine("\t\treturn nativeObj;");
	}
	
}
